/* Copyright 2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * This class is originally from the Grails project and was obtained from
 *
 * http://jira.codehaus.org/browse/GROOVY-2960
 *
 * Changes:
 *
 * 2011-02-13 REC
 * 	- Made target protected so subclasses can access it.
 */
package de.tudarmstadt.ukp.dkpro.lab.groovy.task;

import groovy.lang.Closure;

/**
 * Skeleton implementation of a wrapper class for closures that allows you to intercept invocations
 * of the closure. The wrapper can be used anywhere that the target closure can be used.
 */
public abstract class AbstractClosureProxy
	extends Closure
{
	private static final long serialVersionUID = -3406724476488622300L;
	
	protected Closure target;

	/**
	 * Creates a new instance that wraps the target closure and sends profiling events to the given
	 * profiler log.
	 *
	 * @param closure
	 *            The target closure to wrap.
	 */
	public AbstractClosureProxy(Closure closure)
	{
		super(closure.getOwner(), closure.getThisObject());
		this.target = closure;
	}

	/**
	 * This method is called before the target closure is invoked. This is a passive interceptor, so
	 * you cannot prevent the call to the target closure. You can modify the arguments, though, but
	 * it's not recommended unless you really know what you're doing.
	 *
	 * @param args
	 *            The arguments passed to the closure.
	 */
	protected abstract void doBeforeCall(Object[] args);

	/**
	 * This method is called after the target closure is invoked. It will be triggered whether or
	 * not an exception is thrown by the target closure.
	 *
	 * @param args
	 *            The arguments passed to the closure.
	 */
	protected abstract void doAfterCall(Object[] args);

	/**
	 * Called when a new instance of the proxy needs to be created for the given closure. Usually
	 * the implementation simply creates a new instance of the current class, copying over the
	 * existing proxy properties:
	 *
	 * <pre>
	 *    return new MyClosureProxy(c, this.field1, ...)
	 * </pre>
	 *
	 * @param c
	 *            The closure to wrap/proxy.
	 */
	protected abstract Closure createWrapper(Closure c);

	/**
	 * This is the important one: logs entry and exit of the closure call.
	 */
	@Override
	public Object call(Object[] objects)
	{
		doBeforeCall(objects);

		try {
			return this.target.call(objects);
		}
		finally {
			doAfterCall(objects);
		}
	}

	/**
	 * Compares based on identities, but unlike the standard implementation this one will return
	 * <code>true</code> if the given object is the target closure for this wrapper as well.
	 */
	@Override
	public boolean equals(Object obj)
	{
		return this == obj || this.target == obj;
	}

	@Override
	public int hashCode()
	{
		return this.target.hashCode();
	}

	@Override
	public Closure curry(Object[] objects)
	{
		return createWrapper(this.target.curry(objects));
	}

	@Override
	public boolean isCase(Object o)
	{
		return this.target.isCase(o);
	}

	@Override
	public Closure asWritable()
	{
		return this.target.asWritable();
	}

	@Override
	public Object getProperty(String property)
	{
		return this.target.getProperty(property);
	}

	@Override
	public void setProperty(String s, Object o)
	{
		this.target.setProperty(s, o);
	}

	@Override
	public int getMaximumNumberOfParameters()
	{
		return this.target.getMaximumNumberOfParameters();
	}

	@SuppressWarnings("rawtypes")
	@Override
	public Class[] getParameterTypes()
	{
		return this.target.getParameterTypes();
	}

	@Override
	public Object getDelegate()
	{
		return this.target.getDelegate();
	}

	@Override
	public void setDelegate(Object o)
	{
		this.target.setDelegate(o);
	}

	@Override
	public int getDirective()
	{
		return this.target.getDirective();
	}

	@Override
	public void setDirective(int i)
	{
		this.target.setDirective(i);
	}

	@Override
	public int getResolveStrategy()
	{
		return this.target.getResolveStrategy();
	}

	@Override
	public void setResolveStrategy(int i)
	{
		this.target.setResolveStrategy(i);
	}
}
